#include "TransformAction.h"
#include <QVector>
#include "InterpolateNeighbor.h"



TransformAction::TransformAction (Ptr< Work > pWork) : AbstractAction (pWork)
{
  
}



TransformAction::~TransformAction ()
{

}



void TransformAction::updatePreview (QRect viewport)
{
  Ptr< Layer > pCurLayer = __pTargetWork->getDoc ()->getCurrentLayer ();
  
  QMatrix3x3 & transformMatrix = _transState.transformMatrix;
  QMatrix3x3 & rTransformMatrix = _transState.rTransformMatrix;

  // Get Select polygon after transform
  QPolygon origSelPoly = _pOrigSel->getBounding ();
  QPolygon newSelPoly (4);
  for (int i = 0; i < origSelPoly.size (); ++i)
  {
    QPointF newPoint = transform (origSelPoly.point (i), transformMatrix);
    newSelPoly.setPoint (i, newPoint.x () + 0.5, newPoint.y () + 0.5);
  }

  // Get update region
  QRect newSelRect = getPolygonBounding (newSelPoly);
  QRect srcImgRect = pCurLayer->getRegion ();
  QRect updateRect = newSelRect | srcImgRect;
  bool isPreview = true;
  if (viewport.isEmpty ())
  {
    viewport = updateRect;
    isPreview = false;
  }
  else
    updateRect &= viewport;

  QPoint oNewSel = newSelRect.topLeft ();
  QPoint oOrigSel = _pOrigSel->getBounding ().topLeft ();
  QPoint oSrcImg = srcImgRect.topLeft ();
  QPoint oUpdate = updateRect.topLeft ();
  QPoint oViewport = viewport.topLeft ();

  // create a temperory mask to speedup region decision
  QImage tempMask (newSelRect.size (), QImage::Format_ARGB32);
  tempMask.fill (0);
  QPainter tempMaskPainter (&tempMask);
  tempMaskPainter.setBrush (QColor ((QRgb)0xFFFFFFFF));
  tempMaskPainter.setPen (Qt::NoPen);
  tempMaskPainter.translate (-oNewSel);
  tempMaskPainter.drawPolygon (newSelPoly);

  // transform select mask
  Ptr< QImage > pNewSelMask = new QImage (newSelRect.size (), 
    QImage::Format_Indexed8);
  pNewSelMask->fill (0);

  // create a 2d array to record transform result
  QVector< Ptr< QVector< QPointF > > > transRec (newSelRect.height ());

  for (int v = newSelRect.top (); v <= newSelRect.bottom (); ++v)
  {
    uchar * pNewSelMaskLine = pNewSelMask->scanLine (v - oNewSel.y ());
    QRgb * tempLine = (QRgb *)tempMask.scanLine (v - oNewSel.y ());
    transRec[v - oNewSel.y ()] = new QVector< QPointF > (
      newSelRect.width (), QPointF (-1, -1));
    for (int u = newSelRect.left (); u <= newSelRect.right (); ++u)
    {
      if (tempLine[u - oNewSel.x ()] == 0xFFFFFFFF)
      {
        QPointF origPoint = transform (QPoint (u, v), rTransformMatrix);
        origPoint -= oOrigSel;
        // Use neighbor interpolate method is enough for select mask transform
        pNewSelMaskLine[u - oNewSel.x ()] = InterpolateNeighbor ()
          .getInterpolateResult (*_pOrigSel->getMask (), origPoint);
        (*transRec[v - oNewSel.y ()])[u - oNewSel.x ()] = origPoint;
      }
    }
  }

  // set current select region
  _pCurSel->setBounding (newSelRect);
  _pCurSel->setMask (pNewSelMask);

  // Draw preview
  Ptr< QImage > pPreview = new QImage (viewport.size (), QImage::Format_ARGB32);
  pPreview->fill (0);
  for (int v = updateRect.top (); v <= updateRect.bottom (); ++v)
  {
    QRgb * otherPartLine = NULL;
    if (v >= srcImgRect.top () && v <= srcImgRect.bottom ())
      otherPartLine = (QRgb *)_pOtherPart->scanLine (v - oSrcImg.y ());
    
    QRgb * previewLine = (QRgb *)pPreview->scanLine (v - oViewport.y ());

    for (int u = updateRect.left (); u <= updateRect.right (); ++u)
    {
      // get transform part pixel
      QRgb transPartPixel = 0;
      QPointF origPoint = QPointF (-1, -1);
      if (newSelRect.contains (QPoint (u, v)))
        origPoint = (*transRec[v - oNewSel.y ()])[u - oNewSel.x ()];
      if (origPoint != QPointF (-1, -1))
      {
        transPartPixel = __pTargetWork->getDoc ()->getInterpolate ()
          ->getInterpolateResult (*_pTransPart, origPoint);
      }

      // get other part pixel
      QRgb otherPartPixel = 0;
      if (otherPartLine && u >= srcImgRect.left () && u <= srcImgRect.right ())
      {
        otherPartPixel = otherPartLine[u - oSrcImg.x ()];
      }
      
      previewLine[u - oViewport.x ()] = alphaBlend (transPartPixel, otherPartPixel);
    }
  }
  if (!isPreview)
  {
    pCurLayer->setImage (pPreview);
    pCurLayer->setRegion (updateRect);
  }
  else
    pCurLayer->setPreviewImage (pPreview);
}



void TransformAction::rejectAction ()
{
  if (_pSelBackup != NULL)
    __pTargetWork->setSelectRegion (_pSelBackup);
  __pTargetWork->getDoc ()->getCurrentLayer ()->closePreview ();
}



void TransformAction::applyAction ()
{
  __pTargetWork->getDoc ()->getCurrentLayer ()->closePreview ();
  updatePreview (QRect ());
}



void TransformAction::handleStartEvent ()
{
  Ptr< Layer > pCurLayer = __pTargetWork->getDoc ()->getCurrentLayer ();
  Ptr< QImage > pSrcImage = pCurLayer->getImage ();

  pCurLayer->openPreview ();

  if (__pTargetWork->getSelectRegion () != NULL)
  {
    _pSelBackup = new SelectRegion (*__pTargetWork->getSelectRegion ());
    _pCurSel = __pTargetWork->getSelectRegion ();
  }
  else
  {
    _pSelBackup = NULL;
    _pCurSel = new SelectRegion (pCurLayer->getRegion ());
  }

  // generate a minimize select region
  QRect curSelRect = _pCurSel->getBounding ();
  QRect tempSelRect = curSelRect & pCurLayer->getRegion ();

  if (tempSelRect.isEmpty ())
    OOPTHROW (NoObjectInSelectRegionException);

  // get origin points
  QPoint oCurSel = curSelRect.topLeft ();
  QPoint oTempSel = tempSelRect.topLeft ();
  QPoint oSrcImage = pCurLayer->getRegion ().topLeft ();

  // find the bounding of src image in select region
  QRect minSelRect;
  for (int v = tempSelRect.top (); v <= tempSelRect.bottom (); ++v)
  {
    uchar * curSelLine = _pCurSel->getMask ()->scanLine (v - oCurSel.y ());
    QRgb * srcImageLine = (QRgb *)pSrcImage->scanLine (v - oSrcImage.y ());
    for (int u = tempSelRect.left (); u <= tempSelRect.right (); ++u)
    {
      uchar curSelPixel = curSelLine[u - oCurSel.x ()];
      uchar srcImageAlpha = qAlpha (srcImageLine[u - oSrcImage.x ()]);
      if (curSelPixel != 0 && srcImageAlpha != 0)
      {
        if (minSelRect.isEmpty ())
          minSelRect = QRect (QPoint (u, v), QPoint (u, v));
        minSelRect.setLeft (min (u, minSelRect.left ()));
        minSelRect.setRight (max (u, minSelRect.right ()));
        minSelRect.setTop (min (v, minSelRect.top ()));
        minSelRect.setBottom (max (v, minSelRect.bottom ()));
      }
    }
  }

  if (minSelRect.isEmpty ())
    OOPTHROW (NoObjectInSelectRegionException);

  QPoint oMinSel = minSelRect.topLeft ();

  // draw min select region mask transform part and other part
  Ptr< QImage > pMinSelMask = new QImage (minSelRect.size (), 
    QImage::Format_Indexed8);
  _pTransPart = new QImage (pSrcImage->copy (minSelRect));
  _pOtherPart = new QImage (pSrcImage->copy ());

  for (int v = minSelRect.top (); v <= minSelRect.bottom (); ++v)
  {
    uchar * curSelLine = _pCurSel->getMask ()->scanLine (v - oCurSel.y ());
    QRgb * srcImageLine = (QRgb *)pSrcImage->scanLine (v - oSrcImage.y ());
    uchar * minSelMaskLine = pMinSelMask->scanLine (v - oMinSel.y ());
    QRgb * transPartLine = (QRgb *)_pTransPart->scanLine (v - oMinSel.y ());
    QRgb * otherPartLine = (QRgb *)_pOtherPart->scanLine (v - oSrcImage.y ());
    for (int u = minSelRect.left (); u <= minSelRect.right (); ++u)
    {
      uchar curSelPixel = curSelLine[u - oCurSel.x ()];
      QRgb srcImagePixel = srcImageLine[u - oSrcImage.x ()];
      uchar srcImageA = qAlpha (srcImagePixel);
      uchar srcImageR = qRed (srcImagePixel);
      uchar srcImageG = qGreen (srcImagePixel);
      uchar srcImageB = qBlue (srcImagePixel);

      int u1 = u - oMinSel.x ();
      minSelMaskLine[u1] = curSelPixel;
      transPartLine[u1] = qRgba (srcImageR, srcImageG, 
        srcImageB, curSelPixel * srcImageA / 255);
      otherPartLine[u - oSrcImage.x ()] = qRgba (srcImageR, srcImageG, 
        srcImageB, (255 - curSelPixel) * srcImageA / 255);
    }
  }

  // set the min select region as current select region
  _pCurSel->setBounding (minSelRect);
  _pCurSel->setMask (pMinSelMask);

  // store current selection region
  _pOrigSel = new SelectRegion (*_pCurSel);

  /*~~~cow~~~&~~~apple~~~forever~~~*/

}



void TransformAction::translate (QPoint v)
{
  _transState.translateVal = v;
  _transState.translateMatrix = getTranslateMatrix (_transState.translateVal);
  _transState.rTranslateMatrix = getTranslateMatrix (-_transState.translateVal);

  // get transform matrix and reverse transform matrix
  _transState.transformMatrix = _getTransformMatrix ();
  _transState.rTransformMatrix = _getRTransformMatrix ();
}



void TransformAction::rotate (float angleR)
{
  _transState.rotateValR = angleR;
  _transState.rotateMatrix = getRotateMatrix (_transState.rotateValR);
  _transState.rRotateMatrix = getRotateMatrix (-_transState.rotateValR);

  // get transform matrix and reverse transform matrix
  _transState.transformMatrix = _getTransformMatrix ();
  _transState.rTransformMatrix = _getRTransformMatrix ();
}



void TransformAction::scale (QPointF val)
{
  _transState.scaleVal = val;
  _transState.scaleMatrix = getScaleMatrix (_transState.scaleVal);
  _transState.rScaleMatrix = getScaleMatrix (QPointF (
    1.0 / _transState.scaleVal.x (), 1.0 / _transState.scaleVal.y ()));

  // get transform matrix and reverse transform matrix
  _transState.transformMatrix = _getTransformMatrix ();
  _transState.rTransformMatrix = _getRTransformMatrix ();
}



TransformState & TransformAction::getTrans ()
{
  return _transState;
}



Ptr< SelectRegion > TransformAction::getOrigTransSel ()
{
  return _pOrigSel;
}



Ptr< SelectRegion > TransformAction::getCurTransSel ()
{
  return _pCurSel;
}



QMatrix3x3 TransformAction::_getTransformMatrix ()
{
  QPoint center = _pOrigSel->getBounding ().center ();
  return getTranslateMatrix (center) * _transState.translateMatrix
    * _transState.rotateMatrix * _transState.scaleMatrix 
    * getTranslateMatrix (-center);
}



QMatrix3x3 TransformAction::_getRTransformMatrix ()
{
  QPoint center = _pOrigSel->getBounding ().center ();
  return getTranslateMatrix (center) * _transState.rScaleMatrix 
    * _transState.rRotateMatrix * _transState.rTranslateMatrix 
    * getTranslateMatrix (-center);
}


